﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace XNA_TD
{
    public class LvlInfo
    {
        public float attackDmg, attackRange, fireRate, maxHealth;
        public int preis;
        public List<LvlInfo> nextUpgrades;
        public string text;
        public GegnerTyp angreifbar;

        public LvlInfo(string text, float attackDmg, float attackRange, float fireRate, float maxHealth, int preis, GegnerTyp angreifbar)
        {
            this.text = text;
            this.attackDmg = attackDmg;
            this.attackRange = attackRange;
            this.fireRate = fireRate;
            this.maxHealth = maxHealth;
            this.preis = preis;
            this.angreifbar = angreifbar;
            nextUpgrades = new List<LvlInfo>();
        }

        public LvlInfo Add(LvlInfo info)
        {
            nextUpgrades.Add(info);
            return info;
        }

        public LvlInfo Add(string text, float attackDmg, float attackRange, float fireRate, float maxHealth, int preis, GegnerTyp angreifbar)
        {
            var info = new LvlInfo(text, attackDmg, attackRange, fireRate, maxHealth, preis, angreifbar);
            nextUpgrades.Add(info);
            return info;
        }

    }

    public abstract class GameObject : IRunable, IFixedGameUpdate, IDraw
    {
        public Vector3 position, turretPos;
        protected Model model;
        protected Texture2D texture;
        protected GameObject target = null;
        protected float turretPitch = 0, turretPitchSpeed = 0, turretYaw = 0, turretYawSpeed = 0;
        protected bool canHitDirectly = false;
        public GegnerTyp typ;
        private float cooldown = 0;

        public LvlInfo lvlInfo;
        public float health, gesamtPreis;
        private static Dictionary<string, Texture2D> textures = null;

        protected static Matrix root = Matrix.CreateRotationX(MathHelper.ToRadians(90)) *
                                        Matrix.CreateRotationZ(MathHelper.ToRadians(180));
        protected Matrix rotation;

        protected abstract void Shot();

        public GameObject(Priority priority, Vector3 pos, GegnerTyp typ, string tower)
            :base(priority)
        {
            if (textures == null)
            {
                textures = new Dictionary<string, Texture2D>();
            }
            model = Content.Load<Model>(tower);
            if (textures.ContainsKey(tower))
            {
                texture = textures[tower];
            }
            else
            {
                texture = ((BasicEffect)model.Meshes[0].Effects[0]).Texture;
                textures.Add(tower, texture);
            }
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = shader;
                }
            }
            position = pos;
            turretPos = pos;

            lvlInfo = null;

            this.typ = typ;
        }

        public void ResetHealth()
        {
            health = lvlInfo.maxHealth;
        }

        public bool UpgradeMoeglich(int id)
        {
            if (lvlInfo.nextUpgrades.Count > id && geld >= UpgradeKosten(id))
            {
                return true;
            }
            return false;
        }

        public void Upgrade(int id)
        {
            if (UpgradeMoeglich(id))
            {
                geld -= UpgradeKosten(id);
                gesamtPreis += UpgradeKosten(id);
                lvlInfo = lvlInfo.nextUpgrades[id];
                ResetHealth();
            }
        }

        public virtual void FixedGameUpdate()
        {
            cooldown = Math.Max(cooldown - 10, 0);
            if (target != null)
            {
                Vector3 v = (turretPos - target.position);
                v.Normalize();

                canHitDirectly = true;

                if (turretPitchSpeed > 0)
                {
                    float newPitch = NormalizeAngle(Math.Asin(v.Y));
                    turretPitch = NormalizeAngle(Rotate(turretPitch, newPitch, turretPitchSpeed));
                    if (turretPitch != newPitch)
                    {
                        canHitDirectly = false;
                    }
                }

                if (turretYawSpeed > 0)
                {
                    float newYaw = NormalizeAngle(Math.Atan2(v.X, v.Z));
                    turretYaw = NormalizeAngle(Rotate(turretYaw, newYaw, turretYawSpeed));
                    if (turretYaw != newYaw)
                    {
                        canHitDirectly = false;
                    }
                }

                if (cooldown <= 0 && canHitDirectly)
                {
                    cooldown += 60 / lvlInfo.fireRate * 1000;
                    Shot();
                }

                if (target.health <= 0 || (turretPos - target.position).Length() > lvlInfo.attackRange)
                {
                    target = null;
                }
            }
        }
        protected abstract Matrix[] CalculateAbsoluteBoneTransforms();

        public void Draw()
        {
            shader.CurrentTechnique = shader.Techniques["TexturedShadowed"];
            shader.Parameters["Texture1"].SetValue(texture);
            var boneTransforms = CalculateAbsoluteBoneTransforms();
            // Draw the model.
            foreach (ModelMesh mesh in model.Meshes)
            {
                shader.Parameters["World"].SetValue(boneTransforms[mesh.ParentBone.Index]);
                mesh.Draw();
            }
        }

        public void DrawShadow()
        {
            shader.CurrentTechnique = shader.Techniques["CreateShadow"];
            var boneTransforms = CalculateAbsoluteBoneTransforms();
            
            foreach (ModelMesh mesh in model.Meshes)
            {
                shader.Parameters["World"].SetValue(boneTransforms[mesh.ParentBone.Index]);
                mesh.Draw();
            }
        }

        protected float NormalizeAngle(double ang)
        {
            while (ang >= MathHelper.TwoPi) ang -= MathHelper.TwoPi;
            while (ang < 0) ang += MathHelper.TwoPi;
            return (float)ang;
        }

        protected float Rotate(float from, float to, float speed)
        {
            // Equal, do nothing
            if (from == to) return from;

            // max and min
            float max = Math.Max(from, to);
            float min = Math.Min(from, to);

            // Differences
            float difN = max - min;
            float difI = (MathHelper.TwoPi - max) + min;

            // If normal is lower than inverse
            if (difN <= difI)
                if (from > to)
                    return Rotate(from, to, speed, -1); // links
                else
                    return Rotate(from, to, speed, 1); // rechts
            else
                if (from > to)
                    return Rotate(from, to, speed, 1); // rechts
                else
                    return Rotate(from, to, speed, -1); // links
        }

        private float Rotate(float from, float to, float speed, int direction)
        {
            if (Math.Abs(from - to) < speed || Math.Abs(from - to - MathHelper.TwoPi) < speed)
            {
                return to;
            }
            return from + speed * direction;
        }
        /*
        public float statsMult(int lvl)
        {
            return statsMult(lvl, statsBoost);
        }

        public float statsMult(int lvl, float boost)
        {
            return 1 + (lvl * (lvl + 1) / 2) * boost; // 0 = 0, 1 = 1, 2 = 3, 3 = 6, 4 = 10, 5 = 15
        }

        public int kostenMult(int lvl)
        {
            return 1 + lvl;
        }
        */

        public int UpgradeKosten(int id)
        {
            return lvlInfo.nextUpgrades[id].preis;
        }
        
        public override void Load()
        {
        }

        public override void UnLoad()
        {
            health = 0;
        }

        public void Damage(float dmg)
        {
            if (health <= 0)
                return;
            health -= dmg;
            if (health <= 0)
            {
                Destroyed();
                UnRegister(this);
            }
        }

        public virtual void Destroyed()
        {
        }

        public BoundingSphere[] GetBoundingSpheres()
        {
            var boneTransforms = CalculateAbsoluteBoneTransforms();

            List<BoundingSphere> sphere = new List<BoundingSphere>();
            var total = model.Meshes[0].BoundingSphere.Transform(boneTransforms[model.Meshes[0].ParentBone.Index]);
            foreach (ModelMesh mesh in model.Meshes)
            {
                var pos = mesh.BoundingSphere.Transform(boneTransforms[mesh.ParentBone.Index]);
                sphere.Add(pos);
                total = BoundingSphere.CreateMerged(total, pos);
            }
            sphere.Add(total);
            return sphere.ToArray();
        }

        public BoundingSphere GetBoundingSphere()
        {
            var boneTransforms = CalculateAbsoluteBoneTransforms();
            return model.Meshes[model.Meshes.Count - 1].BoundingSphere.Transform(boneTransforms[model.Meshes[model.Meshes.Count -1].ParentBone.Index]);
        }
    }
}
